package a2;

import java.awt.GraphicsConfiguration;
import java.awt.HeadlessException;
import java.awt.Rectangle;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.UIManager;

public class RotatableJFrame extends JFrame implements ComponentListener {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -2989998119494282360L;
	
	JPanel mainPanel;						// Main panel for window
	
	// JRadioButtons
	private JRadioButton ar169Radio;		// for 16:9 aspect ratio selection
	private JRadioButton ar43Radio;			// for 4:3 aspect ratio selection
	private JRadioButton ar32Radio;			// for 3:2 aspect ratio selection
	
	private JRadioButton landscapeRadio;	// for landscape orientation selection
	private JRadioButton portraitRadio;		// for portrait orientation selection
	
	// ButtonGroups
	private ButtonGroup arGroup;			// Group for aspect ratio RadioButtons
	private ButtonGroup orientationGroup;	// Group for orientation RadioButtons
	
	private List<JComponent> addedComponents;
	private Map<JComponent, A2Constraints> addedComponentsPositions;
	private List<JMenuItem> editMenuItems;
	private List<JMenuItem> removeMenuItems;
	
	private Controller controller;
	
	private A2Layout theLayout;
	
	boolean filled[][];
	
	double aspectRatio;
	boolean isPortrait;
	
	
	/**
	 * Default constructor. 
	 * Constructs a RotatableJFrame with no title or graphics configuration that is initially 
	 * invisible.
	 * @throws HeadlessException if GraphicsEnvironment.isHeadless() returns true.
	 */
	public RotatableJFrame() throws HeadlessException {
		this("", null);		// send to two argument constructor with default values
	}
	
	/**
	 * Constructs an initially invisible RotatableJFrame with the specified 
	 * {@Link GraphicsConfiguration} and no title.
	 * @param gc - The GraphicsConfiguration to use.
	 */
	public RotatableJFrame(GraphicsConfiguration gc) {
		this("", gc);		// send to two arg constructor with passed GraphicsConfiguration
	}
	
	/**
	 * Constructs an initially invisible RotatableJFrame with the specified 
	 * title and no {@Link GraphicsConfiguration}.
	 * @param title - the title to be displayed in the frame's border. 
	 * A null value is treated as an empty string, "".
	 * @throws HeadlessException if GraphicsEnvironment.isHeadless() returns true.
	 */
	public RotatableJFrame(String title) throws HeadlessException {
		this(title, null);  // send to two arg constructor with passed title
	}

	/**
	 * Constructs an initially invisible RotatableJFrame with the specified 
	 * title and {@Link GraphicsConfiguration}.
	 * @param title -  the title to be displayed in the frame's border. 
	 * A null value is treated as an empty string, "".
	 * @param gc - the GraphicsConfiguration to use.
	 */
	public RotatableJFrame(String title, GraphicsConfiguration gc) {
		super(title, gc);

		// instantiate Lists
		addedComponents = new ArrayList<JComponent>(); 	// list of added components
		addedComponentsPositions = new HashMap<JComponent, A2Constraints>();// positions of added components 
		editMenuItems = new ArrayList<JMenuItem>();		// list of edit menu items
		removeMenuItems = new ArrayList<JMenuItem>();	// list of remove menu items
		
		controller = new Controller(this);				// instantiate Controller 
		
		// build the main panel for this JFrame (the one components will be added to)
		mainPanel = new JPanel();
		
		this.aspectRatio = Constants.AR4_3;
		this.isPortrait = false;
		
		theLayout = new A2Layout(mainPanel, 3, 3, aspectRatio);
		mainPanel.setLayout(theLayout);
		//mainPanel.setLayout(new MetroLayout());
		this.addComponentListener(this);
		//WindowResizeAdapter.install(this, SwingConstants.SOUTH_EAST );
		
		try {	
		// Set OS look and feel
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (Exception e) {}
		
		// Set Layout
		//int gridX = 960;
		//int gridY = 540;
		//this.setLayout(new A2Layout(3, 5, (double) gridX/gridY)); 
		//this.setLayout(new java.awt.FlowLayout());
		
		// Add Default Components
		this.addMenu();						// Add a menu
		this.addOrientationToggle();		// Add a RadioButton field toggling orientation
		this.addAspectRatioToggle();		// Add a RadioButton field toggling aspect ratio
		this.addLayoutComponentComboBox();	// Add a ComboBox allowing the user to add components
		
		// Add scrollable mainPanel to the JFrame
		this.add(new JScrollPane(mainPanel,
				JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
				JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED));  
		
	}
	
	private void addMenu() {
		
		// build menu components
		JMenuBar menu = new JMenuBar();
		JMenu addMenu = new JMenu(Constants.TEXT_ADD);
		JMenu editMenu = new JMenu(Constants.TEXT_EDIT);
		JMenu removeMenu = new JMenu(Constants.TEXT_REMOVE);
		
		// Set up Add Menu
		for (String widget : Constants.ALL_WIDGETS) {
			JMenuItem item = new JMenuItem(widget);
			item.addActionListener(controller);
			item.setActionCommand(Constants.TEXT_ADD);
			addMenu.add(item);
		}
		
		// Set up Remove Menu
		JMenuItem removeAll = new JMenuItem(Constants.TEXT_REMOVE_ALL);
		removeAll.addActionListener(controller);
		removeMenu.add(removeAll);
		removeMenu.addSeparator();
		
		// add components to menu
		menu.add(addMenu);
		menu.add(editMenu);
		menu.add(removeMenu);
		
		// set menu
		this.setJMenuBar(menu);
	}

	/** Adds a ComboBox allowing the user to add components */
	private void addLayoutComponentComboBox() {
		// Set up JComboBox
		JComboBox<String> addComboBox = new JComboBox<String>(Constants.ALL_WIDGETS);
		addComboBox.addActionListener(controller);
		
		// Set up JPanel to hold combobox
		JPanel comboBoxPanel = new JPanel();
		comboBoxPanel.setBorder(BorderFactory.createTitledBorder(
							BorderFactory.createEtchedBorder(), // use an etched border
							Constants.TEXT_ADD_WIDGET+ ": "));	// use a predefined title
		
		
		comboBoxPanel.add(addComboBox); // add ComboBox to Panel

		theLayout.addLayoutComponent(comboBoxPanel, new A2Constraints(1,2,3,1));
		mainPanel.add(comboBoxPanel);	// add panel to the frames main panel
		//theLayout.addLayoutComponent(comboBoxPanel, new A2Constraints(100,100));
	}

	/** Adds a JRadioButton field to toggle between aspect ratios */
	private void addAspectRatioToggle() {
		
		// build JRadioButtons
		ar169Radio = new JRadioButton(Constants.ASPECT_RATIO_16_9);
		ar43Radio = new JRadioButton(Constants.ASPECT_RATIO_4_3);
		ar32Radio = new JRadioButton(Constants.ASPECT_RATIO_3_2);
		
		// add action listeners 
		ar169Radio.addActionListener(controller);
		ar43Radio.addActionListener(controller);
		ar32Radio.addActionListener(controller);
		
		arGroup = new ButtonGroup();	// build ButtonGroup
		
		// add JRadioButtons to ButtonGroup, set 4:3 to selected
		arGroup.add(ar169Radio);
		arGroup.add(ar43Radio);
		ar43Radio.setSelected(true);
		arGroup.add(ar32Radio);
		
		// Set up JPanel to hold components
		JPanel arPanel = new JPanel();
		arPanel.setBorder(BorderFactory.createTitledBorder(
							BorderFactory.createEtchedBorder(), // use an etched border
							Constants.ASPECT_RATIO + ": "));	// use a predefined title
		
		// Add JRadioButtons to panel
		arPanel.add(ar169Radio);
		arPanel.add(ar43Radio);
		arPanel.add(ar32Radio);
		
		theLayout.addLayoutComponent(arPanel, new A2Constraints(2,1,1,1));
		
		mainPanel.add(arPanel);				// add panel to the frames main panel
		//theLayout.addLayoutComponent(arPanel, new A2Constraints(10,10));
	}

	/** Adds a JRadioButton field to toggle between orientations */
	private void addOrientationToggle() {
		
		// build JRadioButtons
		landscapeRadio = new JRadioButton(Constants.ORIENTATION_LANDSCAPE);
		portraitRadio = new JRadioButton(Constants.ORIENTATION_PORTRAIT);
		
		// Add action listeners
		landscapeRadio.addActionListener(controller);
		portraitRadio.addActionListener(controller);
		
		orientationGroup = new ButtonGroup();	// build ButtonGroup
		
		// add JRadioButtons to ButtonGroup, setting landscape to selected
		orientationGroup.add(landscapeRadio);
		landscapeRadio.setSelected(true);
		orientationGroup.add(portraitRadio);
		
		// set up JPanel to hold components
		JPanel orientationPanel = new JPanel();
		orientationPanel.setBorder(BorderFactory.createTitledBorder(
									BorderFactory.createEtchedBorder(), // use an etched border
									Constants.ORIENTATION + ": "));	// use a predefined title
		
		// add JRadioButtons to JPanel
		orientationPanel.add(landscapeRadio);
		orientationPanel.add(portraitRadio);
		
		
		theLayout.addLayoutComponent(orientationPanel, new A2Constraints(1,1,1,1));
		
		mainPanel.add(orientationPanel);				// add panel to the frames main panel
		
		
	}
	
	/**
	 * Adds specified JComponent to this JFrame
	 * @param component - the JComponent to add to the JFrame
	 */
	public void addComponent(JComponent component) {
		
		// Add to layout
		this.addedComponentsPositions.put(component, this.addToNextHole(component));
		this.addedComponents.add(component);	// add to vector of added components
		mainPanel.add(component);				// add to panel
		mainPanel.revalidate();					// revalidate the panel to show new component
		
		System.out.println("ADDING: " +this.addedComponents.size() + ". " + 
				component.getClass().getSimpleName());
		// add options to edit or remove this component via the edit and remove menus
		int size = this.getJMenuBar().getMenuCount();
		for (int i = 0; i < size; i++) {
			if (this.getJMenuBar().getMenu(i).getText().equals(Constants.TEXT_EDIT)) {
			// Add a new entry to the edit menu
				JMenu menu = this.getJMenuBar().getMenu(i);
				JMenuItem item = new JMenuItem(this.addedComponents.size() + ". " + 
												component.getClass().getSimpleName());
				item.addActionListener(controller);
				item.setActionCommand(Constants.TEXT_EDIT);
				editMenuItems.add(item);
				menu.add(item);
			} else if (this.getJMenuBar().getMenu(i).getText().equals(Constants.TEXT_REMOVE)) {
			// Add a new entry to the remove menu
				JMenu menu = this.getJMenuBar().getMenu(i);
				JMenuItem item = new JMenuItem(this.addedComponents.size() + ". " + 
												component.getClass().getSimpleName());
				item.addActionListener(controller);
				item.setActionCommand(Constants.TEXT_REMOVE);
				removeMenuItems.add(item);
				menu.add(item);
			}
		}
	}

	private A2Constraints addToNextHole(JComponent component) {
		int result[] = new int[2];
		result[0] = -1;
		result[1] = -1;
		
		A2Constraints cons = null;			
		for (int i = 1; i < 4; i++) {
			for (int j = 1; j < 4; j++) {
				try {
					if ((theLayout.gridMap()[i][j] == 0)) {
						cons = new A2Constraints(i,j,1,1);
						System.out.println("Added to " + i + "," + j);
						break;
					}
				} catch (Exception e) {
					System.out.println("Error at: " + i + ", " + j + ": ");
					e.printStackTrace();
					System.exit(-1);
				}
								
			}
		} 
		
		if (cons != null) {
			theLayout.addLayoutComponent(component, cons);
		}
		
		return cons;
		
		
	}

	/**
	 * Removes a widget widget at specified index
	 * @param index - The index of the widget to remove
	 */
	public void removeWidget(int index) {
		JComponent removed = this.addedComponents.remove(index);	// get the widget
		mainPanel.remove(removed);								// remove the widget
		
		this.theLayout.removeLayoutComponent(removed);
		
		
		// repaint window
		this.revalidate();
		this.repaint();
		
		// remove component from Map
		this.addedComponentsPositions.remove(removed);
		
		// remove references from the menus
		for (int i = 0; i < this.getJMenuBar().getMenuCount(); i++) {
			if (this.getJMenuBar().getMenu(i).getText().equals(Constants.TEXT_EDIT)) {
			// remove from edit menu
				System.out.println("Removing index: " + index);
				System.out.println(this.editMenuItems.get(index).getText());
				JMenu menu = this.getJMenuBar().getMenu(i);
				
				this.editMenuItems.remove(index);	// remove from list of edit menu items
				
				// remake menu
				menu.removeAll();					
				for (int j = 0; j < this.editMenuItems.size(); j++) {
					JMenuItem item = this.editMenuItems.get(j);
					item.setText("" + (j+1) + item.getText().substring(1));
					menu.add(item);
				}
				
			} else if (this.getJMenuBar().getMenu(i).getText().equals(Constants.TEXT_REMOVE)) {
			// remove from remove menu
				//this.getJMenuBar().getMenu(i).remove(this.removeMenuItems.remove(index));
				JMenu menu = this.getJMenuBar().getMenu(i);
				
				this.removeMenuItems.remove(index);	// remove from list of edit menu items
				
				// remake menu
				menu.removeAll();			
				JMenuItem removeAll = new JMenuItem(Constants.TEXT_REMOVE_ALL);
				removeAll.addActionListener(controller);
				menu.add(removeAll);
				menu.addSeparator();
				for (int j = 0; j < this.removeMenuItems.size(); j++) {
					JMenuItem item = this.removeMenuItems.get(j);
					item.setText("" + (j+1) + item.getText().substring(1));
					menu.add(item);
				}
			}
		}

		this.theLayout.layoutContainer(this);
	}

	public void removeAllWidgets() {
		while(this.addedComponents.size() > 0) {
			this.removeWidget(0);
		}
		
	}
	
	public void setSize(int width, int height) {
		
		if ((float) width/height != aspectRatio) {
			float approxHeight = (float) ((float) width * (1 / aspectRatio));
			height = (int) approxHeight;
		}
		super.setSize(width, height);
	}

	@Override
	public void componentHidden(ComponentEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void componentMoved(ComponentEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void componentResized(ComponentEvent e) {
		
	    Rectangle b = e.getComponent().getBounds();
	    int width = b.width;
	    int height = b.height;
	    if ((float) width/height != aspectRatio) {
			float approxHeight = (float) ((float) width * (1 / aspectRatio));
			height = (int) approxHeight;
		}
	    
	    e.getComponent().setBounds(b.x, b.y, width, height);
	    //this.setSize(width, height);
	    
	    this.revalidate();

	}

	@Override
	public void componentShown(ComponentEvent e) {
		// TODO Auto-generated method stub
		
	}
	
	/**
	 * Sets the current aspect ratio to the specified aspect ratio
	 * @param ratio - the aspect ratio to set the window to
	 */
	public void setAspectRatio(double ratio) {
		this.aspectRatio = ratio;
		this.setSize(this.getWidth(), 1);
	}
	
	/**
	 * Sets orientation to portrait if currently in landscape, 
	 * and landscape if current in portrait
	 */
	public void setOrientation(boolean isPortrait) {
		if (isPortrait != this.isPortrait) {
			this.isPortrait = isPortrait;
			System.out.println("Current aspect = " +this.aspectRatio);
			System.out.println("New Aspect = " + 1f/this.aspectRatio);
			this.setAspectRatio(1f/aspectRatio);
		}
		/*if (this.isPortrait) {
			// Window is in portrait, let's check if aspect ratio is valid, then change
			if ((this.aspectRatio == (double) 1f/Constants.AR16_9) ||  
				(this.aspectRatio == (double) 1f/Constants.AR4_3) ||
				(this.aspectRatio == (double) 1f/Constants.AR3_2)){
				System.out.println("Portrait, changing AR to " + 1f/aspectRatio);
				this.setAspectRatio(1f/aspectRatio);
			}
		} else {
			// Window is in landscape, let's check if aspect ratio is valid, then change
			if ((this.aspectRatio == (double) Constants.AR16_9) ||  
				(this.aspectRatio == (double) Constants.AR4_3) ||
				(this.aspectRatio == (double) Constants.AR3_2)){
				System.out.println("Landscape, changing AR to " + 1f/aspectRatio);
				this.setAspectRatio(1f/aspectRatio);
			}
		}*/
	}
	
	/**
	 * Gets whether this frame is portrait or not
	 * @return - true if portrait, false if landscape
	 */
	public boolean getOrientation() {
		return this.isPortrait;
	}
	
	/**
	 * Edits the x, y, width, and height coordinates of the widget at the specified index
	 * @param index - the index of the widget to edit
	 */
	public void editWidget(int index) {
		JComponent editted = this.addedComponents.get(index);	// get the widget
		A2Constraints constraints = this.addedComponentsPositions.get(editted);
		
		System.out.println(editted.getClass().getSimpleName() + ": " +
							constraints.xPos() + " " + 
							constraints.yPos() + " " + 
							constraints.width() + " " + 
							constraints.height() + " ");
		
		new EditDialog(this, editted, constraints);
	}
	
	/**
	 * Edits the specified components constraints and re-adds to the window
	 * @param editted - the component to edit
	 * @param newConstraints - the new constraints of the component
	 */
	public void editComponentConstraints(JComponent editted, A2Constraints newConstraints) {

		this.theLayout.removeLayoutComponent(editted);
		this.theLayout.layoutContainer(this);
		this.theLayout.addLayoutComponent(editted, newConstraints);
		this.revalidate();
		this.repaint();
	}
	

}
